#ifndef SWEGL_MATH_VECTOR3_H
#define SWEGL_MATH_VECTOR3_H

namespace swegl
{

template<typename T>
class Vector3
{
public:
	// default contructor, do not initialize components
	Vector3();
	Vector3(T x, T y, T z);
	Vector3(const Vector3 &rhs);

	Vector3 &set(T x, T y, T z);
	Vector3 &operator = (const Vector3 &rhs);
	Vector3 operator - () const;

	T operator [] (uint index) const;
	T &operator [] (uint index);

	void operator += (const Vector3 &rhs);
	void operator -= (const Vector3 &rhs);
	void operator *= (const Vector3 &rhs);
	void operator *= (T s);
	void operator /= (const Vector3 &rhs);
	void operator /= (T s);
	
	Vector3 operator + (const Vector3 &rhs) const;
	Vector3 operator - (const Vector3 &rhs) const;
	T operator * (const Vector3 &rhs) const;
	Vector3 operator * (T s) const;
	Vector3 operator / (T s) const;
	
	bool operator == (const Vector3 &rhs) const;
	bool operator != (const Vector3 &rhs) const;
	
	Vector3 cross(const Vector3 &rhs) const;
	Vector3 &cross(const Vector3 &lhs, const Vector3 &rsh);
	
	float normalize();
	static Vector3 Normalize(const Vector3 &v);
	
	const T *toPtr() const;
	T *toPtr();

	T length() const;
	T sqrLength() const;
	
	// clamp v to the range [min..max]
	static Vector3 Clamp(const Vector3 &v, const Vector3 &min, const Vector3 &max);
	// return a new vector made up of the smallest components of v0 and v1
	static Vector3 Minimize(const Vector3 &v0, const Vector3 &v1);
	// return a new vector made up of the largest components of v0 and v1
	static Vector3 Maximize(const Vector3 &v0, const Vector3 &v1);
	// return true if any component is < in v0 than in v1
	static bool IsAnyLess(const Vector3 &v0, const Vector3 &v1);
	// return true if all components are < in v0 than in v1
	static bool IsAllLess(const Vector3 &v0, const Vector3 &v1);
	// return true if any component is <= in v0 than in v1
	static bool IsAnyLessEqual(const Vector3 &v0, const Vector3 &v1);
	// return true if all components are <= in v0 than in v1
	static bool IsAllLessEqual(const Vector3 &v0, const Vector3 &v1);
	// return true if any component is > in v0 than in v1
	static bool IsAnyGreater(const Vector3 &v0, const Vector3 &v1);
	// return true if all components are > in v0 than in v1
	static bool IsAllGreater(const Vector3 &v0, const Vector3 &v1);
	// return true if any component is >= in v0 than in v1
	static bool IsAnyGreaterEqual(const Vector3 &v0, const Vector3 &v1);
	// return true if all components are >= in v0 than in v1
	static bool IsAllGreaterEqual(const Vector3 &v0, const Vector3 &v1);
	// return true if any component is == in v0 and in v1
	static bool IsAnyEqual(const Vector3 &v0, const Vector3 &v1);
	// return true if all components of v0 and v1 are equal (equivalent to v0 == v1 operator)
	static bool IsAllEqual(const Vector3 &v0, const Vector3 &v1);
	// return true if all components of v0 and v1 are almost equal with the given epsilon (one epsilon per component)
	static bool IsAlmostEqual(const Vector3 &v0, const Vector3 &v1, const Vector3 &epsilon);
	// return true if all components of v0 and v1 are almost equal with the given epsilon
	static bool IsAlmostEqual(const Vector3 &v0, const Vector3 &v1, float epsilon);
	
	static const Vector3 Zero;
	static const Vector3 UnitX;
	static const Vector3 UnitY;
	static const Vector3 UnitZ;
	static const Vector3 NegativeUnitX;
	static const Vector3 NegativeUnitY;
	static const Vector3 NegativeUnitZ;
	static const Vector3 UnitScale;

	T x;
	T y;
	T z;
};

template<typename T>
Vector3<T> operator * (T s, const Vector3<T> &rhs);

typedef Vector3<float> Vec3;

template<typename T>
const Vector3<T> Vector3<T>::Zero(0, 0, 0);
template<typename T>
const Vector3<T> Vector3<T>::UnitX(1, 0, 0);
template<typename T>
const Vector3<T> Vector3<T>::UnitY(0, 1, 0);
template<typename T>
const Vector3<T> Vector3<T>::UnitZ(0, 0, 1);
template<typename T>
const Vector3<T> Vector3<T>::NegativeUnitX(-1, 0, 0);
template<typename T>
const Vector3<T> Vector3<T>::NegativeUnitY(0, -1, 0);
template<typename T>
const Vector3<T> Vector3<T>::NegativeUnitZ(0, 0, -1);
template<typename T>
const Vector3<T> Vector3<T>::UnitScale(1, 1, 1);

template<typename T>
inline Vector3<T>::Vector3()
{
}

template<typename T>
inline Vector3<T>::Vector3(T x, T y, T z) : x(x), y(y), z(z)
{
}

template<typename T>
inline Vector3<T>::Vector3(const Vector3 &rhs) : x(rhs.x), y(rhs.y), z(rhs.z)
{
}

template<typename T>
inline Vector3<T> &Vector3<T>::set(T x, T y, T z)
{
	this->x = x;
	this->y = y;
	this->z = z;
	return *this;
}

template<typename T>
inline Vector3<T> &Vector3<T>::operator = (const Vector3 &rhs)
{
	this->x = rhs.x;
	this->y = rhs.y;
	this->z = rhs.z;
	return *this;
}

template<typename T>
inline Vector3<T> Vector3<T>::operator - () const
{
	return Vector3(- this->x, - this->y, - this->z);
}

template<typename T>
inline T Vector3<T>::operator [] (uint index) const
{
	return (&x)[index];
}
	
template<typename T>
inline T &Vector3<T>::operator [] (uint index)
{
	return (&x)[index];
}

template<typename T>
inline void Vector3<T>::operator += (const Vector3 &rhs)
{
	this->x += rhs.x;
	this->y += rhs.y;
	this->z += rhs.z;
}

template<typename T>
inline void Vector3<T>::operator -= (const Vector3 &rhs)
{
	this->x -= rhs.x;
	this->y -= rhs.y;
	this->z -= rhs.z;
}

template<typename T>
inline void Vector3<T>::operator *= (const Vector3 &rhs)
{
	this->x *= rhs.x;
	this->y *= rhs.y;
	this->z *= rhs.z;
}

template<typename T>
inline void Vector3<T>::operator *= (T s)
{
	this->x *= s;
	this->y *= s;
	this->z *= s;
}

template<typename T>
inline void Vector3<T>::operator /= (const Vector3 &rhs)
{
	this->x /= rhs.x;
	this->y /= rhs.y;
	this->z /= rhs.z;
}

template<typename T>
inline void Vector3<T>::operator /= (T s)
{
	this->x /= s;
	this->y /= s;
	this->z /= s;
}

template<typename T>
inline Vector3<T> Vector3<T>::operator + (const Vector3 &rhs) const
{
	return Vector3(this->x + rhs.x, this->y + rhs.y, this->z + rhs.z);
}

template<typename T>
inline Vector3<T> Vector3<T>::operator - (const Vector3 &rhs) const
{
	return Vector3(this->x - rhs.x, this->y - rhs.y, this->z - rhs.z);
}

template<typename T>
inline T Vector3<T>::operator * (const Vector3 &rhs) const
{
	return this->x * rhs.x + this->y * rhs.y + this->z * rhs.z;
}

template<typename T>
inline Vector3<T> Vector3<T>::operator * (T s) const
{
	return Vector3(this->x * s, this->y * s, this->z * s);
}

template <typename T>
inline Vector3<T> operator * (T s, const Vector3<T> &rhs)
{
	return rhs * s;
}

template<typename T>
inline Vector3<T> Vector3<T>::operator / (T s) const
{
	return Vector3(this->x / s, this->y / s, this->z / s);
}

template<typename T>
inline bool Vector3<T>::operator == (const Vector3 &rhs) const
{
	return this->x == rhs.x && this->y == rhs.y && this->z == rhs.z;
}

template<typename T>
inline bool Vector3<T>::operator != (const Vector3 &rhs) const
{
	return this->x != rhs.x || this->y != rhs.y || this->z != rhs.z;
}

template<typename T>
inline Vector3<T> Vector3<T>::cross(const Vector3 &rhs) const
{
	return Vector3(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x);
}

template<typename T>
inline Vector3<T> &Vector3<T>::cross(const Vector3 &lhs, const Vector3 &rhs)
{
	x = lhs.y * rhs.z - lhs.z * rhs.y;
	y = lhs.z * rhs.x - lhs.x * rhs.z;
	z = lhs.x * rhs.y - lhs.y * rhs.x;
	return *this;
}

template<typename T>
inline float Vector3<T>::normalize()
{
	float len = length();
	sweglAssert(len > 0);
	float invLength = 1 / len;
	this->x *= invLength;
	this->y *= invLength;
	this->z *= invLength;
	return len;
}

template<typename T>
inline Vector3<T> Vector3<T>::Normalize(const Vector3 &v)
{
	float l = 1 / v.length();
	return Vector3(v.x * l, v.y * l, v.z * l);
}

template<typename T>
inline const T *Vector3<T>::toPtr() const
{
	return &x;
}

template<typename T>
inline T *Vector3<T>::toPtr()
{
	return &x;
}

template<typename T>
inline T Vector3<T>::length() const
{
	return Sqrt(x * x + y * y + z * z);
}

template<typename T>
inline T Vector3<T>::sqrLength() const
{
	return x * x + y * y + z * z;
}

template<typename T>
inline Vector3<T> Vector3<T>::Clamp(const Vector3 &v, const Vector3 &min, const Vector3 &max)
{
	return Vector3(Clamp(v.x, min.x, max.x), Clamp(v.y, min.y, max.y), Clamp(v.z, min.z, max.z));
}

template<typename T>
inline Vector3<T> Vector3<T>::Minimize(const Vector3 &v0, const Vector3 &v1)
{
	return Vector3(Min(v0.x, v1.x), Min(v0.y, v1.y), Min(v0.z, v1.z));
}

template<typename T>
inline Vector3<T> Vector3<T>::Maximize(const Vector3 &v0, const Vector3 &v1)
{
	return Vector3(Max(v0.x, v1.x), Max(v0.y, v1.y), Max(v0.z, v1.z));
}

template<typename T>
inline bool Vector3<T>::IsAnyLess(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x < v1.x || v0.y < v1.y || v0.z < v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAllLess(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x < v1.x && v0.y < v1.y && v0.z < v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAnyLessEqual(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x <= v1.x || v0.y <= v1.y || v0.z <= v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAllLessEqual(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x <= v1.x && v0.y <= v1.y && v0.z <= v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAnyGreater(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x > v1.x || v0.y > v1.y || v0.z > v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAllGreater(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x > v1.x && v0.y > v1.y && v0.z > v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAnyGreaterEqual(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x >= v1.x || v0.y >= v1.y || v0.z >= v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAllGreaterEqual(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x >= v1.x && v0.y >= v1.y && v0.z >= v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAnyEqual(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x == v1.x || v0.y == v1.y || v0.z == v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAllEqual(const Vector3 &v0, const Vector3 &v1)
{
	return v0.x == v1.x && v0.y == v1.y && v0.z == v1.z;
}

template<typename T>
inline bool Vector3<T>::IsAlmostEqual(const Vector3 &v0, const Vector3 &v1, const Vector3 &epsilon)
{
	return v0.x >= v1.x - epsilon.x && v0.x <= v1.x + epsilon.x
		&& v0.y >= v1.y - epsilon.y && v0.y <= v1.y + epsilon.y
	    && v0.z >= v1.z - epsilon.z && v0.z <= v1.z + epsilon.z;
}

template<typename T>
inline bool Vector3<T>::IsAlmostEqual(const Vector3 &v0, const Vector3 &v1, float epsilon)
{
	return v0.x >= v1.x - epsilon && v0.x <= v1.x + epsilon
		&& v0.y >= v1.y - epsilon && v0.y <= v1.y + epsilon
	    && v0.z >= v1.z - epsilon && v0.z <= v1.z + epsilon;
}

}

#endif
